package com.battle.service.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.collection.CollectionUtil;
import com.battle.pojo.entity.User;
import com.battle.pojo.event.user.LoginEvent;
import com.battle.pojo.param.login.AccountLoginParam;
import com.battle.pojo.vo.login.TokenVo;
import com.battle.service.bean.security.Blacklist;
import com.battle.service.repository.in.RoleRepository;
import com.battle.service.repository.in.UserRepository;
import com.battle.service.service.in.LoginService;
import com.battle.service.utils.ConstantsUtil;
import com.battle.service.utils.PasswordUtil;
import com.battle.service.utils.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import nirvana.core.exceptions.BizException;
import nirvana.core.properties.SecurityProperties;
import nirvana.core.security.SecurityCredential;
import nirvana.core.utils.JwtTools;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 【描 述】：
 * 【环 境】：J2SE 17
 * 【详 细】：
 *
 * @author 李松林
 * @version v1.0 2024/5/3 新建
 * @since 2024/5/3
 */
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    private static final String AUTH_CODE_SALT = "auth_code_salt";

    private final UserRepository userRepository;

    private final RoleRepository roleRepository;

    private final ApplicationEventPublisher publisher;

    private final SecurityProperties securityProperties;

    private final HttpServletRequest request;

    private final HttpServletResponse response;

    private final Blacklist blacklist;
    private final StringRedisTemplate stringRedisTemplate;

    public LoginServiceImpl(UserRepository userRepository, RoleRepository roleRepository, ApplicationEventPublisher publisher, SecurityProperties securityProperties, HttpServletRequest request, HttpServletResponse response, Blacklist blacklist, StringRedisTemplate stringRedisTemplate) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.publisher = publisher;
        this.securityProperties = securityProperties;
        this.request = request;
        this.response = response;
        this.blacklist = blacklist;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取用户token
     * @param user
     * @return
     */
    public TokenVo getToken(User user) {
        Long userId = user.getId();
        String account = user.getAccounts();
        List<Long> roleIds = roleRepository.getRoleIdsListByUserId(userId);

        //设置一个非refresh的Token
        SecurityCredential credential = new SecurityCredential();
        credential.setId(userId);
        credential.setUsername(account);

        if (CollectionUtil.isNotEmpty(roleIds)){
            credential.setRoleIds(roleIds);
        }
        credential.setUserType(1);
        credential.setLoginTime(Timestamp.valueOf(LocalDateTime.now()));

        return getToken(credential);
    }

    /**
     * 获取 token vo
     *
     * @param credential
     * @return
     */
    public TokenVo getToken(SecurityCredential credential) {
        credential.setIsRefresh(Boolean.FALSE);
        String token = JwtTools.generateToken(credential, securityProperties.getExpire());


        //设置一个刷新token
        credential.setIsRefresh(Boolean.TRUE);
        String refreshToken = JwtTools.generateToken(credential, securityProperties.getExpire() + securityProperties.getRefreshExpire());


        TokenVo tokenVo = new TokenVo();
        tokenVo.setToken(token);
        tokenVo.setRefreshToken(refreshToken);


        return tokenVo;
    }


    /**
     * 检查登录用户
     * @param user
     * @return
     */
    public TokenVo checkLoginUser(User user){
        if (user.getIsDisable()) {
            throw new BizException("当前用户已被禁用");
        }
        return getToken(user);
    }

    /**
     * 测试用 用户id获取token
     *
     * @param id
     * @return
     */
    @Override
    public TokenVo testIdLogin(Long id) {

        User user = userRepository.getByIdCheck(id);

        //登录成功
/*        LoginEvent event = new LoginEvent();
        event.setLoginSuccess(Boolean.TRUE);
        event.setUserId(id);
        publisher.publishEvent(event);*/

        return getToken(user);
    }

    /**
     * 获取验证码
     */
    @Override
    public void getAuthCode() {
        //设置相应类型,告诉浏览器输出的内容为图片
        response.setContentType("image/jpeg");

        //设置响应头信息，告诉浏览器不要缓存此内容
        response.setHeader("pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        //验证码
        String authCode = null;

        //获取验证码图片方法
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 100);
        authCode = lineCaptcha.getCode().toLowerCase();

        String salt = UUID.randomUUID().toString();

        //将生成的图形验证码存到redis
        userRepository.saveAuthCode(authCode, salt);
        log.debug("生成的验证码： " + authCode);

        //将盐写入返回头
        response.setHeader(AUTH_CODE_SALT, salt);
        response.setHeader("Access-Control-Expose-Headers", AUTH_CODE_SALT);

        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            lineCaptcha.write(outputStream);
        } catch (Exception e) {
            throw new BizException("获取验证码错误");
        } finally {

            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    throw new BizException("获取验证码错误");
                }
            }
        }
    }

    /**
     * 账号登录
     *
     * @param param
     * @return
     */
    @Override
    public TokenVo accountLogin(AccountLoginParam param) {
        String authCode = param.getAuthCode();
        String salt = request.getHeader(AUTH_CODE_SALT);
        if (salt==null){
            throw new BizException("salt未填写");
        }

        if (!"9999".equals(authCode)) {
            if (!userRepository.checkAuthCode(authCode, salt)) {
                throw new BizException("验证码错误");
            }
        }

        String account = param.getAccount();
        User user = userRepository.getUserByAccount(account);
        if (user == null) {
            throw new BizException("账号错误");
        }

        Long userId = user.getId();
        String password = param.getPassword();
        String userPassword = user.getPasswords();
        Boolean loginSuccess = PasswordUtil.passwordConfirm(StringUtil.jointString(userId.toString(), password), userPassword);


        //登录成功
        LoginEvent event = new LoginEvent();
        event.setLoginSuccess(loginSuccess);
        event.setUserId(userId);
        publisher.publishEvent(event);


        if (!loginSuccess) {
            throw new BizException("密码错误");
        }
        return checkLoginUser(user);
    }

    /**
     * 刷新token
     *
     * @return
     */
    @Override
    public TokenVo refresh() {
        /*
        校验token逻辑
        并从token中获取用户信息
         */
        //获取token
        String tokenHttpHeader = securityProperties.getRefreshTokenHttpHeader();
        String token = request.getHeader(tokenHttpHeader);


        //无刷新token  则返回异常
        if (!StringUtils.hasText(token) || !token.startsWith(securityProperties.getTokenHead())) {
            throw new BizException("刷新token错误");
        }

        token = token.substring(securityProperties.getTokenHead().length() + 1);

        //检查是否在黑名单中
        if (blacklist.check(token)) {
            throw new BizException("刷新token错误");
        }

        SecurityCredential credential = JwtTools.getUserInfoFromToken(token);

        //如果token不是refresh token 页不能刷新
        if (!credential.getIsRefresh()) {
            throw new BizException("刷新token错误");
        }
        Integer expire = securityProperties.getExpire() + securityProperties.getRefreshExpire();
        long time = expire.longValue();

        //保存到黑名单
        blacklist.save(token, time, TimeUnit.MINUTES);
        return getToken(credential);
    }
}
